/* 
 * To add new filetypes:
 *	The file Opener.table is a list of suffix/command pairs
 *	that are used to identify and unpack various kinds of files.
 *	You need to edit this table, and add an icon for the new type:
 *  add ...
 *	- a line to Opener.table, 
 *	       (formerly, new code in Controller.m:unpackFile())
 *	- a utility in utils/..., and in Makefile.postamble
 *	- an icon to the TIFF suitcase in Opener.nib
 * - suffixes to 'types[]' in - openRequest
 * and please mail me improvements if you do.
 *
 * Michael Hawley
 * mike@media-lab.mit.edu
 * Copyright (c) MIT Media Laboratory, September 1991
 *
 * Revisions:
 *   new color icons
 *   fixed case-insensitivity problem for archive file creation
 *   fixed bug where app incorrectly recognized .ps and .eps extensions
 *   fixed bug where app didn't properly prepare .PS and .PS for Preview
 *   
 *   added gz file type
 *   added arj file type
 *   method registerWindow:toPort: is obsolete, so treat it so
 *   reconciled code to NeXTSTEP 3.1
 *
 *   added -removeTmpFiles, to clean up the scratch space.
 *   added several new types (lzh, arc, zip, etc).
 *   made it table-driven.
 *   added kludge to try automatic unpacking of comp.sources material
 *   added tar.Z-making function.
 *
 *   added preference panel, plus panel and code to permit selecting
 *      an arbitrary archiving command.
 *   added monitoring, "suggestion", defaults.
 *   safe quoting of funny characters in filenames.
 *   added archive-building commands.
 *   fixed .tar.z problem
 *   command output now generally goes to console, 
 *      except "...done" messages, as when building an archive,
 *      which pop up in a panel.
 *   added hack in tmp() to fix the problem of "/tmp/O_foo.app"
 *      directories not opening.
 *
 *   Thanks to Garance Drosehn (gad@eclipse.its.rpi.edu) for some
 *   of these improvements.
 */

#include <sys/types.h>
#include <sys/stat.h>
#import <dpsclient/dpsclient.h>
#include <ctype.h>
#import "Controller.h"
#import "DefaultHandler.h"
#import "DirPanel.h"
#import "Process.h"

@implementation Controller

id _defaults;
extern char TmpDir[];

int 
err(a,b,c,d) char *a,*b,*c,*d; {
	char t[1024];
	extern id NXApp;
	sprintf(t,a,b,c,d);
	return NXRunAlertPanel([NXApp appName],t,0,0,0)==1;
}

static id _pb2, me;

@interface Speaker(ObsoleteMethods)
- (int)registerWindow:(int)windowNum toPort:(port_t)aPort;
- (int)unregisterWindow:(int)windowNum;
@end

- setPackButton2:b {
	packButton2 = _pb2 = b;
	return self;
}

#include "monitor.m"

char *
tmp(char *s,  char *suffix, int isDir) {
/* 
 * return a path for a temporary file
 * named "/tmp/O_[n_]file", where 'file' is 's';
 * e.g., /a/b/foo.bar, .bar => /tmp/O_foo
 * if 'isDir', map a "." in the suffix to "_" to ensure that
 * the directory wrapper will open properly.
 */
	static char t[1024];
	char *p, u[1024];
	int k = 1;
	
	strcpy(u,s);
	if (*suffix) u[strlen(u) - strlen(suffix)] = '\0';
	if (p = rindex(u,'/')) p++;
	else p = s;
	if (isDir) {
		char *x = rindex(p,'.');
		if (x) *x = '_';
	}
	sprintf(t,"%s/O_%s",TmpDir,p);
	while (access(t,0)==0) sprintf(t,"%s/O_%d_%s",TmpDir,k++,p);
	return t;
}

char *
path(char *s)
/*
 * if 's' is in [NXArgv[0]], return [NXArgv[0]]/s.
 * (for looking up internal commands and files in .../Opener.app/...).
 */
{
	char t[1024], *q, *r;
	static char p[1024];
	extern char **NXArgv;
	*t = '\0';
	sscanf(s,"%s",t);
	if (!*t || *t=='(') return s;
	r = s + strlen(t);
	strcpy(p,*NXArgv);
	if (q=rindex(p,'/'))
		strcpy(q+1,t);
	else
		strcpy(p,t);
	if (access(p,0)==0){
		strcpy(p+strlen(p),r);
		return p;
	}
	return s;
}

char *
skipsp(char *s) {
	while (*s==' ' || *s=='\t' || *s == '\n') ++s;
	return s;
}

int
blank(char *s) { /* true if 's' is blank */
	while (*s == ' ' || *s=='\t' || *s == '\n') ++s;
	return !*s;
}

char *
save(char *s) {
	char *p = (char *)malloc(strlen(s)+1);
	if (p) strcpy(p,s);
	return p;
}

void
stripcomment(s) char *s; { /* truncate 's' at a comment character ('#') */
	char *p = index(s,'#');
	if (p && (p==s || p[-1] != '\\')) *p = '\0';
}

char *
strindex(char *s, char *t) { /* return ptr to first match of 't' in 's' */
	int n = strlen(t);
	
	if (s)
		while (*s)
			if (!strncmp(s, t, n)) return s;
			else s++;
	return (char *)0;
}

// Case-insensitive version of equal().  
// Commented out by Denise Blakeley 08/93 Opener 3.1.1.
// int 
// equal(char *s, char *t) { /* true if 's' & 't' are equal (case-insens.) */
//	char a, b;
//	while (*s && *t){
//		a = isupper(*s)? tolower(*s) : *s;
//		b = isupper(*t)? tolower(*t) : *t;
//		s++, t++;
//		if (a != b) return 0;
//	}
//	return  (*s || *t)? 0 : 1;
// }

/* Case-sensitive version of equal(). */
/* Added by Blakeley 08/93 Opener 3.1.1. */
int 
equal(char *s, char *t) { /* true if 's' & 't' are equal (case-sens.) */
	char a, b;
	while (*s && *t){
		a = *s;
		b = *t;
		s++, t++;
		if (a != b) return 0;
	}
	return  (*s || *t)? 0 : 1;
}

int 
tailmatch(char *s, char *t){ /* true if t appears at end of s */
	int tn = strlen(t), sn = strlen(s);
	if (tn > sn) return 0;
	return equal(s+(sn-tn), t);
}

char *
shstrcat(char *s, char *t){ // strcat, but quote shell metachars
	register char *p;
	
	while (*s) s++;
	p=t;
	if (*p=='~') *s++='\\';	// csh "feature"
	while (*p) {
		if (*p<=' '||*p>'~'||index(";&()|<>\\'\"$*?[]^!", *p)) *s++='\\';
		*s++=*p++;
	}
	*s='\0';
	return t;
}

void
subfname(char *s, char *a, char *b) { /* like 'substr', but assumes the "b"
				      string is a filename that may need
				      special-to-shell characters quoted */
	char q[8192], quoted_b[2048];
	char *p = s;
	int n = strlen(a);
	quoted_b[0] = '\0';
	shstrcat(quoted_b, b);
	for (;*p;p++){
		if (*p == *a && strncmp(p,a,n)==0){
			strcpy(q,p+n);
			strcpy(p,quoted_b);
			strcpy(p+strlen(quoted_b),q);
			p += strlen(quoted_b)-1;
		}
	}
}

#define MaxLen 256
#define MaxSuf 64
#define MaxTab 64

char Suffix[MaxTab][MaxSuf],
     Command[MaxTab][MaxLen];
int NT = 0;

char PSuffix[MaxTab][MaxSuf],
     PCommand[MaxTab][MaxLen];
int PNT = 0;

int fdTime(int f){ /* return size of file 'f' */
	struct stat b;
	fstat(f, &b);
	return b.st_mtime;
}

void
clearPackItems() {
	int i, n = [[_pb2 target] count];
	for (i=0;i<n;i++){
		[[_pb2 target] removeItemAt:i];
	}
}

void
addPackItem(char *s) {
	id c;
	c = [[_pb2 target] addItem:s];
	[c setTarget:me]; 
	[c setAction:@selector(setPackSuffix:)];
}

void
resetPackItems() {
	int i;
	clearPackItems();
	for (i=0;i<PNT;i++)
		addPackItem(PSuffix[i]);
}

void
addUnpackItem(char *s) {
	id c;
	c = [[_pb2 target] addItem:s];
	[c setTarget:me]; 
	[c setAction:@selector(setUnpackSuffix:)];
}

void
loadTable() {
/*
 * Unpacking files is table-driven.
 * The file "Opener.table" is of the form
 *    suffix command
 * like
 *    .Z	uncompress < $f > $t
 * When opening files, the table is checked to
 * find a matching command for the given suffix.
 */
	char s[1024];
	FILE *f;
	static int first = 1;
	static int t = 0;
	int pack = 0;
	
	strcpy(s,path("Opener.table"));
	if (!first){
		if (f=fopen(s,"r")){
			int tt = fdTime(fileno(f));
			fclose(f);
			if (tt == t) return;
			t = tt; 				// table has changed -- reread it
			NT=PNT=0;
			clearPackItems();
			printf("rereading table\n");
		} else
			return;
	}
	first = 0;
	if (f = fopen(s,"r")){
		char *p, *q;
		t = fdTime(fileno(f));
		while (fgets(s,sizeof s,f)){
			stripcomment(s);
			if (blank(s)) continue;
			if (strncmp(s,"Unpack:",7)==0) continue;
			if (strncmp(s,"Pack:",5)==0){ pack++; continue; }
			if (pack){
				for (p=skipsp(s), q=PSuffix[PNT]; 
					*p && *p != ' ' && *p != '\t' && (q-PSuffix[PNT])<(MaxSuf-1);
					*q++ = *p++) ;
				*q = '\0';
				for (p = skipsp(p), q=PCommand[PNT];
					*p && (*p != '\n') && (q-PCommand[PNT])<(MaxLen-1);
					*q++ = *p++) ;
				*q='\0';
				addPackItem(PSuffix[PNT]);
// printf("[%s]: %s\n",PSuffix[PNT],PCommand[PNT]);
				if (!blank(PSuffix[PNT])&& !blank(PCommand[PNT]) && PNT < MaxTab)
					++PNT;
			} else {
				for (p=skipsp(s), q=Suffix[NT]; 
					*p && *p != ' ' && *p != '\t' && (q-Suffix[NT])<(MaxSuf-1);
					*q++ = *p++) ;
				*q = '\0';
				for (p = skipsp(p), q=Command[NT];
					*p && (*p != '\n') && (q-Command[NT])<(MaxLen-1);
					*q++ = *p++) ;
				*q='\0';
// printf("[%s]: %s\n",Suffix[NT],Command[NT]);
				if (!blank(Suffix[NT]) && !blank(Command[NT]) && NT < MaxTab)
					++NT;
			}    
		}
		fclose(f);
	} else {
#define C(a,b) strcpy(Suffix[NT],a), strcpy(Command[NT],b), NT++
		C(".tar", "unpack: tar xf $f");
		C(".shar","unpack: sed '1,/[^:#]/d' $f | /bin/sh");
		C(".uu",  "unpack: uudecode < $f; open *");
		C(".lzh", "unpack: $p/xlharc xf $f");
		C(".arc", "unpack: $p/arc ox $f");
		C(".sit", "unpack: $p/unsit -u $f");
		C(".bin", "unpack: $p/unsit -u $f");
		C(".hqx", "unpack: $p/mcvert $f; $p/unsit *.sit.*");
		C(".zip", "unpack: $p/unzip $f");
		C(".zoo", "unpack: $p/booz x $f");
		C(".Z",   "uncompress < $f > $t");
#undef C
#define C(a,b) strcpy(PSuffix[PNT],a), strcpy(PCommand[PNT],b), addPackItem(a), PNT++
		C(".tar", "(cd $d; tar cf - $F > $t.tar; echo $t.tar done)");
	}
}

int
findSuffix(char *s) {
	char *p = s + strlen(s);
	int i;
	loadTable();
	for (i=0;i<NT;i++)
		if (equal(Suffix[i],p-strlen(Suffix[i])))
			/* changed from equal() by Blakeley */
			return i;
	return -1;
}

int
findPSuffix(char *s) {
	char *p = s + strlen(s);
	int i;
	loadTable();
	for (i=0;i<PNT;i++)
		if (equal(PSuffix[i],p-strlen(PSuffix[i]))) 
			/* changed from equal() by Blakeley */
			return i;
	return -1;
}

void
ex_unpack(char *s) {
	char t[1024];
	if (strncmp(s,"unpack: ",8)) return;
	strcpy(t,skipsp(s+8));
	strcpy(s,"mkdir $t; cd $t; "); strcat(s,t);
}

void
substr(char *s, char *a, char *b) { /* like 'sub', but with strings */
	char q[8192];
	char *p = s;
	int n = strlen(a);
	for (;*p;p++){
		if (*p == *a && strncmp(p,a,n)==0){
			strcpy(q,p+n);
			strcpy(p,b);
			strcpy(p+strlen(b),q);
			p += strlen(b)-1;
		}
	}
}

void
expand(char *s, int i, char *c) {
/*
 * 's' is the current filename.
 * 'i' is the index in the Suffix/Command table.
 * 'c' is the command to fill;
 * Replace "unpack: ..." with the standard unpack command
 *         $f with the filename, 
 *         $t with the temp
 */
	char *T, *p, S[MaxSuf], q[1024], *r;
	int isDir = 0;
	
	strcpy(c,Command[i]);
	strcpy(S,s+strlen(s)-strlen(Suffix[i]));
	if (strindex(c,"mkdir $t") || strindex(c,"unpack: ")) isDir = 1;
	T = tmp(s,Suffix[i],isDir); 
	
	ex_unpack(c);     /* rewrite "unpack: ..." */
	
	p = c;	      /* prepend the $p/... paths */
	while (p = strindex(p,"$p/")){
		sscanf(p,"%s",q);
		r = path(q+3);
		substr(p,q,r);
	}
	
	if (!strindex(c,"open ")){
		if (isDir) strcat(c,"; (cd $t; open . )"); // make sure directory opens
		else strcat(c,"; open $t");
	}
	
	subfname(c,"$f",s); /* expand the abbreviations */
	subfname(c,"$t",T);
	subfname(c,"$s",S);
}

void
getTmpName(char *s, char *t) {
/*
 * Copy a /tmp name for the comp.sources archive into 't'.
 * if 's' is ...comp.sources/.../foo/part00.Z ==> /tmp/O_foo
 *           ...comp.sources/.../foo.Z        ==> /tmp/O_foo
 */
	char *p = rindex(s,'/'), q[1024], c;
	int n;

	if (!p) return (void)strcpy(t,s);
	strcpy(q,p+1);
	c = q[4]; q[4]='\0';

	if (equal(q,"part") && isdigit(c)){ /* skip to previous */
		*p='\0';
		p = rindex(s,'/');
		if (!p) p=s;
		else p++;
	} else p++;

	strcpy(t,p);
	n = findSuffix(t);
	p = (n== -1)? ".Z" : Suffix[n];
	if (t[strlen(t)-strlen(p)]=='.')
		t[strlen(t)-strlen(p)] = '\0';
	strcpy(t,tmp(t,p,0));
}

int 
System(fmt, a,b,c,d,e,f,g) char *fmt,*a,*b,*c,*d,*e,*f,*g; {
	char t[8192];
	sprintf(t,fmt,a,b,c,d,e,f,g);
	printf("! %s\n",t);
	return system(t);
}

void 
openFile(char *s){ /* open 's' in workspace */
	int ok = 0;
	id p = [NXApp appSpeaker];
	
	[p setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	[p openTempFile:s ok:&ok];
	if (!ok) err("Couldn't open file: %s",s);
}

void 
previewFile(char *s) { /* open 's' in the default PostScript previewer */
/* This function worked over for 3.1.1 by Blakeley; it never worked before
 * but it does now. */
	char *a = (char *)0; 
	char *index;
	char head[1024];
	int ok = 0;
	id p = [NXApp appSpeaker];
	
	/* get handle on the user's favorite Preview application */
	if (!a) a = "Preview";
	[p setSendPort:NXPortFromName(a, NULL)];
	
	getTmpName(s, head);
	index = rindex(&head[0], '.');	
	if (tailmatch(s, ".PS"))
		strcpy(index+1, "ps");
	else
		strcpy(index+1, "eps");
	
	System("cp %s %s", s, head);
	[p openFile: head ok:&ok];
	if (!ok) err("Couldn't open file: %s",s);
}

int 
confirm(a,b,c,d) char *a,*b,*c,*d; {
	char t[1024];
	extern id NXApp;
	sprintf(t,a,b,c,d);
	return NXRunAlertPanel([NXApp appName],t,"Yes","No",NULL)==1;
}

- setTmp:sender {
	char *s = (char *)[sender stringValueAt:0];
	if (!s || !*s || access(s,0)){
		if (s && *s) err("Couldn't access '%s'",s);
		[sender setStringValue:TmpDir at:0];
		s = (char *)[sender stringValueAt:0];
	} else
		strcpy(TmpDir,s);
	[_defaults writeDefaults:self];
	return self;
}

int
unpackFile(s) char *s; {
	int i;
	char c[2048];
	i = findSuffix(s);
	if (i != -1){ /* we found a suffix and command in the table */
		expand(s,i,c);
		[me system:c]; // System("(%s)&",c);
	} else
	if (tailmatch(s,".PS" ) || tailmatch(s,".EPS")) /* catch some PS files */
		previewFile(s);
	else
		err("Couldn't open '%s'",s);
	return 1;
}

- (BOOL)appAcceptsAnotherFile:sender {
	return YES;
}

int 
isCompSourceArchive(s) char *s; {
	return ((strindex(s,"comp.sources") || strindex(s,"mod.sources") ||
				strindex(s,"net.sources")) &&
		equal(s+strlen(s)-2,".z") &&
		!strindex(s,"index") && !strindex(s,"Index") && !strindex(s,"INDEX") &&
		!strindex(s,"files") && !strindex(s,"Files") && !strindex(s,"FILES"));
}


#define MaxPend 512
char *Pending[MaxPend];
int NP = 0;

void 
reap(DPSTimedEntry n, double now, char *userData){
	int i, np;
	char x[1024], head[1024], c[8192], *p, q[1024], *r;
	
	DPSRemoveTimedEntry(n);
	np = NP;
	if (!NP) return;
	NP = 0;
	
	if (confirm("Try to build %s...?",Pending[0])){
	strcpy(x,Pending[0]);
		getTmpName(x,head);
	sprintf(c,"(mkdir $t; cp ");
	for (i=0; i<np; i++) 
		strcat(c,Pending[i]), strcat(c," ");
	sprintf(c+strlen(c)," $t; $p/builder $t; open $t $t/errors)&");
	
	p = c;	      /* prepend the $p/... paths */
	while (p = strindex(p,"$p/")){
		sscanf(p,"%s",q);
		r = path(q+3);
		substr(p,q,r);
	}
	substr(c,"$t",head);
	System("%s",c);
	} else {
	for (i=0;i<np;i++) 
		unpackFile(Pending[i]);
	}
	for (i=0;i<np;i++) free(Pending[i]);
}

void
pending(s) char *s;
/*
 * One or more files from a comp.sources archive are being selected.
 * Put them on a queue -- reap() will unpack them specially.
 */
{
	if (NP == MaxPend)
		return (void)err("Too many pending files!  Try quitting.");
	Pending[NP++] = save(s);
	DPSAddTimedEntry(4.0, (DPSTimedEntryProc)reap, 0, 10);
}

- (int)appOpenFile:(const char *)filename type:(const char *)aType {
	me = self;
	if (isCompSourceArchive(filename))
		pending(filename);
	else
		unpackFile((char *)filename);
	return 1;
}

- openRequest:sender {
	const char *directory, *const *files;
	static const char *const types[] = {
			"tar", "TAR", "tar.z", "tar.Z", "TAR.Z", "tar-z", "tar-Z", "TAR-Z",
			"tar.gz", "tar.GZ", "TAR.GZ", "tar-gz", "tar-GZ", "TAR-GZ",
			"shar","SHAR","shar.z","shar.Z","SHAR.Z","shar-z","shar-Z","SHAR-Z",
			"shar.gz", "shar.GZ", "SHAR.GZ", "shar-gz", "shar-GZ", "SHAR-GZ",
			"PS", "EPS", "PS-Z", "ps-Z", "ps-z", "EPS-Z", "eps-Z", "eps-z",
			"PS-GZ", "ps-GZ", "ps-gz", "EPS-GZ", "eps-GZ", "eps-gz",
			"arc", "Arc", "ARC",
			"bin", "Bin", "BIN",
			"lzh", "Lzh", "LZH",
			"hqx", "Hqx", "HQX",
			"sit", "Sit", "SIT",
			"zip", "Zip", "ZIP",
			"zoo", "Zoo", "ZOO",
			"uu", "UU",
			"z", "Z",
			"gz", "Gz", "GZ",
			"arj", "Arj", "ARJ",
			"compressed", "Compressed", "COMPRESSED",
			NULL};
	id p = [[OpenPanel new] allowMultipleFiles:YES];
	char name[512];
	
	if ([p runModalForTypes:types]){
		files = [p filenames];		// list of multiple files
		directory = [p directory];
		while (files && *files) {
			strcpy(name, directory );
			strcat(name, "/" );
			strcat(name, *files );
			files++;
			unpackFile(name);
		}
	}
	return self;
}

- (BOOL)scratchFiles {
	char t[1024];
	FILE *p;
	char s[1024];
	BOOL b = NO;
	
	if (strcmp(TmpDir,"/tmp"))
		sprintf(t,"ls /tmp/O_* %s/O_*",TmpDir);
	else
		sprintf(t,"ls /tmp/O_*");
	p = popen(t,"r");
	if (p){
		if (fgets(s,sizeof s,p) && strncmp(s,"No match",8)) b = YES;
		pclose(p);
	}
	return b;
}

- removeTmpFiles:sender {
	if ([self scratchFiles] && 
		confirm("Remove the scratch files in %s?",TmpDir)){
		if (strcmp(TmpDir,"/tmp"))
			System("rm -rf /tmp/O_* %s/O_* &",TmpDir);
		else
			System("rm -rf /tmp/O_* &");
	}
	return self;
}

- appWillTerminate:sender {
	unsigned int wn;
	NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &wn);
	[self removeTmpFiles:sender];
	[[NXApp appSpeaker] unregisterWindow:wn];
	return self;
}

- appDidInit:sender {
	char subj[512] = "Opener-";
	unsigned int wn;
	id s = [NXApp appSpeaker];
	
	me = self;
	_defaults = [DefaultHandler new];
	[tmpDir setStringValue:TmpDir at:0];
	
	strcat(subj,[version stringValue]);
	strcat(subj,"-newuser");
	OpenerMonitor(subj);
	
	NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &wn);
	[s setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	[s registerWindow:wn toPort:[[NXApp appListener] listenPort]];
	return self;
}

char *iconPathList = (char *)0;

- (int)iconEntered:(int)windowNum at:(double)x :(double)y
    iconWindow:(int)iconWindowNum 
    iconX:(double)iconX iconY:(double)iconY
    iconWidth:(double)iconWidth 
    iconHeight:(double)iconHeight
    pathList:(char *)pathList
{
	if (!iconPathList || strcmp(iconPathList, pathList)) {
		free(iconPathList);
		iconPathList = save(pathList);
	}
	return 0;
}

void
stot(char *s, char **t, char c) { /* split 's' into a table 't' */
	char *p;
	while (p = index(s,c)){
		*p++ = '\0';
		*t++ = s;
		s = p;
	}
	*t++ = s;
	*t = (char *)0;
}

void
trimdir(char *d, char **t) { /* put longest leading directory of t into d */
	int i, n;
	char *p,*q;
	
	*d = '\0';
	p = rindex(t[0],'/');
	if (p){
		*p='\0';
		strcpy(d,t[0]); strcat(d,"/");
		*p='/';
	} else return ;

	if (t[1]){
		for (i=0;t[i];i++){
			for (p=d,q=t[i]; *p && *q && *p == *q; p++, q++);
			if (!*p) continue;
			*p = '\0';
		}
	}
	n = strlen(d);
	for (i=0;t[i];i++) t[i] += n;
}

- processOutput:(char *)s {
	err("%s",s);
	return self;
}
	
- system:(char *)s { // exec 's' and processOutput pops up a panel with output
	[Process new:s delegate:self];
	return self;
}

void
pexpand(i,c,d,f,F,t)
    char *c, *d, *f, *F, *t;
    int i;
/*
 * like expand, but for packing:
 * 'i' is the index in the Suffix/Command table.
 * 'c' is the command to fill;
 * 'd' is the prefix directory
 * 'F' is the files without 'd' removed
 * 'f' is the files with 'd' removed
 * 't' is the files with 'd' removed
 */
{
	char *p, q[1024], *r; 
	strcpy(c,PCommand[i]);
	
	p = c;	      /* prepend the $p/... paths */
	while (p = strindex(p,"$p/")){
		sscanf(p,"%s",q);
		r = path(q+3);
		substr(p,q,r);
	}
	
	substr(c,"$F",F);
	substr(c,"$f",f);
	substr(c,"$d",d);
	substr(c,"$t",t);
}


void
expand_files(char *s, char *p, char *dir) {
	char *t[1024], x[8192];
	int i;
	strcpy(x,s);
	stot(x,t,'\t');
	if (dir) trimdir(dir,t);
	*p = '\0';
	for (i=0;t[i];i++){
		if (i > 0) strcat(p," ");
		shstrcat(p,t[i]);
		p += strlen(p);
	}
}

- setDirectory:sender {
	id p = [SavePanel new];
	char *s;
	
	[[[p contentView]
	findViewWithTag:NX_OPTITLEFIELD]
	setStringValue:"Unpack files in:"];
	
	if ([p dirPanelRunModal] && (s = (char *)[p directory])){
		[tmpDir setStringValue:s at:0];
		strcpy(TmpDir,s);
		[_defaults writeDefaults:self];
	}
	return self;
}

static char Files[8192];

char *
getText(id t, char *s) {
	id d = [t docView];
	int n = [d textLength];
	char *p;
	*s = '\0';
	[d getSubstring:s start:0 length:[d textLength]];
	s[n] = '\0';
	for (p=s; *p; p++) if (*p == '\n') *p = ' ';
	return s;
}

void
setText(id t, char *s) {
	id w = [t window], d = [t docView];
	static NXPoint origin = {0.0,0.0};
	[w disableFlushWindow];
	[d setMonoFont:YES];
	[d setText:s];
	[d setSel:0:999999];
	[d setSelGray:0.0];
	[d selectNull];
	[d scrollPoint:&origin];
	[t display];
	[[w reenableFlushWindow] flushWindow];
	NXPing();
}

int
setSuffixes(char *s) {
	char *p = s + strlen(s);
	int i, r = -1;
	loadTable();
	clearPackItems();
	for (i=0;i<NT;i++)
		if (equal(Suffix[i],p-strlen(Suffix[i]))){
			if (r == -1) r = i;
			addUnpackItem(Suffix[i]);
		}
	if (r >= 0) [[_pb2 selectedCell] setTitle:Suffix[r]];
	return r;
}

char MoreFiles[8192] = "";
int Reset = 1;

- setPackCommand {
	char t[1024], f[4096], F[4096], dir[1024], *p, files[8192];
	char c[8192], *curSuffix, *curCmd;
	int i;
	
	Again:
		curSuffix = (char *)[[packButton2 selectedCell] title];
		curCmd = (char *)[[packButton selectedCell] title];
		strcpy(files,Files);
		
		if (strcmp(curCmd,"create")==0){
			if (Reset) resetPackItems();
			Reset = 1;
			curSuffix = (char *)[[packButton2 selectedCell] title];
			i = findPSuffix(curSuffix);
			if (i == -1){
				[packPanel orderOut:self];
				err("Unknown archive format: %s",curSuffix);
				return 0;
			}
		
			expand_files(files,f,dir);
			expand_files(files,F,0);
			sscanf(f,"%s",t); stripnl(t);
			if (p = rindex(t,'/')) strcpy(t,p+1);
			strcat(t,curSuffix);
			p = tmp(t,"",0); strcpy(t,p);
		
			pexpand(i,c,dir,f,F,t);
			setText(packCmdText,c);
		} else {
			if (index(files,'\t')){
				char *p;
				p = index(files,'\t');
				*p = '\0';
				strcpy(MoreFiles,p+1);
				stripnl(files);
			} else {
				*MoreFiles = '\0';
			}
			expand_files(files,f,0);
			i = setSuffixes(files);
			if (i == -1){
				extern void NXBeep();
				[[packButton selectedCell] setTitle:"create"];
				NXBeep();
				resetPackItems();
				NXPing();
				goto Again;
			}
			
			expand(f,i,c);
			setText(packCmdText,c);
		}
	return self;
}

- setPackOrUnpack:sender {
	char *s = (char *)[[sender selectedCell] title];
	[[packButton selectedCell] setTitle:s];
	if (strcmp(s,"create")==0)
		[packPanel setTitle:"Make new archive"];
	else
		[packPanel setTitle:"Unpack archive"];
	resetPackItems();
	[self setPackCommand];
	return self;
}

- setUnpackSuffix:sender {
	char *s = (char *)[[sender selectedCell] title];
	[[packButton2 selectedCell] setTitle:s];
	NXPing();
	[self setPackCommand];
	return self;
}

- setPackSuffix:sender {
	char *s = (char *)[[sender selectedCell] title];
	extern char ArchiveFormat[];
	strcpy(ArchiveFormat,s);
	[_defaults writeDefaults:self];
	[[packButton2 selectedCell] setTitle:s];
	Reset = 0; 						// bandaid
	NXPing();
	[self setPackCommand];
	return self;
}

- packFiles:(char *)files {
	strcpy(Files,files);
	if (!*MoreFiles){
		if (index(Files,'\t') || findSuffix(Files) == -1){
			[packPanel setTitle:"Make new archive"];
			[[packButton selectedCell] setTitle:"create"];
		} else {
			[packPanel setTitle:"Unpack archive"];
			[[packButton selectedCell] setTitle:"unpack"];
		}
	}
	[packPanel makeKeyAndOrderFront:self];
/*	Reset = 0; */
	[self setPackCommand];
	return self;
}

- cancelPack:sender {
	[packPanel orderOut:sender];
	*MoreFiles = '\0';
	return self;
}

- pack:sender {
	char c[8192];
	getText(packCmdText,c);
	if (!blank(c)) [self system:c];
	[packPanel orderOut:sender];
	if (*MoreFiles) [self packFiles:MoreFiles];
	return self;
}

- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag {
	[self packFiles:iconPathList];
	*flag = YES;
	return 0;
}

- help:sender {
	openFile(path("README.rtfd"));
	return self;
}

- editTable:sender {
	if (confirm("Edit the command table that controls Opener?\n(Unix experts only)"))
		System("open %s",path("Opener.table"));
	return self;
}

@end
